001 /* 002 * Copyright 2003-2005 The Apache Software Foundation 003 * Copyright 2005 Stephen McConnell 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package net.dpml.cli.option; 018 019 import java.util.ArrayList; 020 import java.util.Collections; 021 import java.util.Comparator; 022 import java.util.HashSet; 023 import java.util.Iterator; 024 import java.util.List; 025 import java.util.ListIterator; 026 import java.util.Set; 027 028 import net.dpml.cli.Argument; 029 import net.dpml.cli.DisplaySetting; 030 import net.dpml.cli.Group; 031 import net.dpml.cli.OptionException; 032 import net.dpml.cli.WriteableCommandLine; 033 import net.dpml.cli.resource.ResourceConstants; 034 035 /** 036 * A Parent implementation representing normal options. 037 * 038 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 039 * @version 1.0.0 040 */ 041 public class DefaultOption extends ParentImpl 042 { 043 /** 044 * The default token used to prefix a short option 045 */ 046 public static final String DEFAULT_SHORT_PREFIX = "-"; 047 048 /** 049 * The default token used to prefix a long option 050 */ 051 public static final String DEFAULT_LONG_PREFIX = "--"; 052 053 /** 054 * The default value for the burstEnabled constructor parameter 055 */ 056 public static final boolean DEFAULT_BURST_ENABLED = true; 057 058 private final String m_preferredName; 059 private final Set m_aliases; 060 private final Set m_burstAliases; 061 private final Set m_triggers; 062 private final Set m_prefixes; 063 private final String m_shortPrefix; 064 private final boolean m_burstEnabled; 065 private final int m_burstLength; 066 067 /** 068 * Creates a new DefaultOption 069 * 070 * @param shortPrefix the prefix used for short options 071 * @param longPrefix the prefix used for long options 072 * @param burstEnabled should option bursting be enabled 073 * @param preferredName the preferred name for this Option, this should 074 * begin with either shortPrefix or longPrefix 075 * @param description a description of this Option 076 * @param aliases the alternative names for this Option 077 * @param burstAliases the aliases that can be burst 078 * @param required whether the Option is strictly required 079 * @param argument the Argument belonging to this Parent, or null 080 * @param children the Group children belonging to this Parent, ot null 081 * @param id the unique identifier for this Option 082 * @throws IllegalArgumentException if the preferredName or an alias isn't 083 * prefixed with shortPrefix or longPrefix 084 */ 085 public DefaultOption( 086 final String shortPrefix, final String longPrefix, final boolean burstEnabled, 087 final String preferredName, final String description, final Set aliases, 088 final Set burstAliases, final boolean required, final Argument argument, 089 final Group children, final int id ) 090 throws IllegalArgumentException 091 { 092 super( argument, children, description, id, required ); 093 094 m_shortPrefix = shortPrefix; 095 m_burstEnabled = burstEnabled; 096 m_burstLength = shortPrefix.length() + 1; 097 m_preferredName = preferredName; 098 099 if( aliases == null ) 100 { 101 m_aliases = Collections.EMPTY_SET; 102 } 103 else 104 { 105 m_aliases = Collections.unmodifiableSet( new HashSet( aliases ) ); 106 } 107 108 if( burstAliases == null ) 109 { 110 m_burstAliases = Collections.EMPTY_SET; 111 } 112 else 113 { 114 m_burstAliases = Collections.unmodifiableSet( new HashSet( burstAliases ) ); 115 } 116 117 final Set newTriggers = new HashSet(); 118 newTriggers.add( m_preferredName ); 119 newTriggers.addAll( m_aliases ); 120 newTriggers.addAll( m_burstAliases ); 121 m_triggers = Collections.unmodifiableSet( newTriggers ); 122 123 final Set newPrefixes = new HashSet( super.getPrefixes() ); 124 newPrefixes.add( m_shortPrefix ); 125 newPrefixes.add( longPrefix ); 126 m_prefixes = Collections.unmodifiableSet( newPrefixes ); 127 128 checkPrefixes( newPrefixes ); 129 } 130 131 /** 132 * Indicates whether this Option will be able to process the particular 133 * argument. 134 * 135 * @param commandLine the CommandLine object to store defaults in 136 * @param argument the argument to be tested 137 * @return true if the argument can be processed by this Option 138 */ 139 public boolean canProcess( 140 final WriteableCommandLine commandLine, final String argument ) 141 { 142 return 143 ( argument != null ) 144 && ( 145 super.canProcess( commandLine, argument ) 146 || ( 147 ( argument.length() >= m_burstLength ) 148 && m_burstAliases.contains( 149 argument.substring( 0, m_burstLength ) ) 150 ) 151 ); 152 } 153 154 /** 155 * Process the parent. 156 * @param commandLine the CommandLine object to store defaults in 157 * @param arguments the ListIterator over String arguments 158 * @exception OptionException if an error occurs 159 */ 160 public void processParent( WriteableCommandLine commandLine, ListIterator arguments ) 161 throws OptionException 162 { 163 final String argument = (String) arguments.next(); 164 165 if( m_triggers.contains( argument ) ) 166 { 167 commandLine.addOption( this ); 168 arguments.set( m_preferredName ); 169 } 170 else if( m_burstEnabled && ( argument.length() >= m_burstLength ) ) 171 { 172 final String burst = argument.substring( 0, m_burstLength ); 173 if( m_burstAliases.contains( burst ) ) 174 { 175 commandLine.addOption( this ); 176 //HMM test bursting all vs bursting one by one. 177 arguments.set( m_preferredName ); 178 179 if( getArgument() == null ) 180 { 181 arguments.add( m_shortPrefix + argument.substring( m_burstLength ) ); 182 } 183 else 184 { 185 arguments.add( argument.substring( m_burstLength ) ); 186 } 187 arguments.previous(); 188 } 189 else 190 { 191 throw new OptionException( 192 this, ResourceConstants.CANNOT_BURST, argument ); 193 } 194 } 195 else 196 { 197 throw new OptionException( 198 this, 199 ResourceConstants.UNEXPECTED_TOKEN, 200 argument ); 201 } 202 } 203 204 /** 205 * Identifies the argument prefixes that should trigger this option. This 206 * is used to decide which of many Options should be tried when processing 207 * a given argument string. 208 * 209 * The returned Set must not be null. 210 * 211 * @return The set of triggers for this Option 212 */ 213 public Set getTriggers() 214 { 215 return m_triggers; 216 } 217 218 /** 219 * Identifies the argument prefixes that should be considered options. This 220 * is used to identify whether a given string looks like an option or an 221 * argument value. Typically an option would return the set [--,-] while 222 * switches might offer [-,+]. 223 * 224 * The returned Set must not be null. 225 * 226 * @return The set of prefixes for this Option 227 */ 228 public Set getPrefixes() 229 { 230 return m_prefixes; 231 } 232 233 /** 234 * Checks that the supplied CommandLine is valid with respect to this 235 * option. 236 * 237 * @param commandLine the CommandLine to check. 238 * @throws OptionException if the CommandLine is not valid. 239 */ 240 public void validate( WriteableCommandLine commandLine ) 241 throws OptionException 242 { 243 if( isRequired() && !commandLine.hasOption( this ) ) 244 { 245 throw new OptionException( 246 this, 247 ResourceConstants.OPTION_MISSING_REQUIRED, 248 getPreferredName() ); 249 } 250 super.validate( commandLine ); 251 } 252 253 /** 254 * Appends usage information to the specified StringBuffer 255 * 256 * @param buffer the buffer to append to 257 * @param helpSettings a set of display settings @see DisplaySetting 258 * @param comp a comparator used to sort the Options 259 */ 260 public void appendUsage( 261 final StringBuffer buffer, final Set helpSettings, final Comparator comp ) 262 { 263 // do we display optionality 264 final boolean optional = 265 !isRequired() 266 && helpSettings.contains( DisplaySetting.DISPLAY_OPTIONAL ); 267 268 final boolean displayAliases = 269 helpSettings.contains( DisplaySetting.DISPLAY_ALIASES ); 270 271 if( optional ) 272 { 273 buffer.append( '[' ); 274 } 275 276 buffer.append( m_preferredName ); 277 278 if( displayAliases && !m_aliases.isEmpty() ) 279 { 280 buffer.append( " (" ); 281 282 final List list = new ArrayList( m_aliases ); 283 Collections.sort( list ); 284 for( final Iterator i = list.iterator(); i.hasNext();) 285 { 286 final String alias = (String) i.next(); 287 buffer.append( alias ); 288 if( i.hasNext() ) 289 { 290 buffer.append( ',' ); 291 } 292 } 293 buffer.append( ')' ); 294 } 295 296 super.appendUsage( buffer, helpSettings, comp ); 297 298 if( optional ) 299 { 300 buffer.append( ']' ); 301 } 302 } 303 304 /** 305 * The preferred name of an option is used for generating help and usage 306 * information. 307 * 308 * @return The preferred name of the option 309 */ 310 public String getPreferredName() 311 { 312 return m_preferredName; 313 } 314 }